﻿using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Animation;
using KuiHuaBaoDian.Services.Asset;
using UnityEngine;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.Animation {

    /// <summary>
    /// 动画
    /// </summary>
    public interface IAnimation {

        /// <summary>
        /// 动画句柄
        /// </summary>
        AnimationHandle Handle { get; }

        /// <summary>
        /// 加载
        /// </summary>
        /// <param name="scheme">动画方案</param>
        /// <returns>异步任务</returns>
        UniTask Load(IAnimationScheme scheme);

        /// <summary>
        /// 播放参数<paramref name="state"/>指示状态的动画剪辑
        /// </summary>
        /// <param name="state">状态</param>
        /// <returns>异步任务</returns>
        UniTask Play(AnimationState state);

        /// <summary>
        /// 播放参数<paramref name="state"/>指示状态的覆盖后动画剪辑
        /// </summary>
        /// <param name="state">状态</param>
        /// <param name="clipName">剪辑名称</param>
        /// <param name="assetKey">资产键</param>
        /// <returns>异步任务</returns>
        UniTask PlayOverride(AnimationState state, AnimationClipName clipName, string assetKey);

        /// <summary>
        /// 向控制器发送触发参数
        /// </summary>
        /// <param name="trigger">触发</param>
        /// <returns>异步任务</returns>
        UniTask Trigger(AnimationTrigger trigger);

        /// <summary>
        /// 向控制器发送触发参数，且触发成功则播放覆盖后动画剪辑
        /// </summary>
        /// <param name="trigger">触发</param>
        /// <param name="clipName">剪辑名称</param>
        /// <param name="assetKey">资产键</param>
        /// <returns>异步任务</returns>
        UniTask TriggerOverride(AnimationTrigger trigger, AnimationClipName clipName, string assetKey);
    }

    [Implement(typeof(IAnimation))]
    internal sealed class KHBD_Animation : KHBD_Object, IAnimation, IAnimationInternal {

        public AnimationHandle Handle { get; private set; } = new AnimationHandle { GUID = Guid.NewGuid() };

        private GameObject m_GameObject;

        private Animator m_Animator;
        private AssetHandle<RuntimeAnimatorController> m_ControllerAssetHandle;
        private IAnimationScheme m_Scheme;

        private CancellationTokenSource m_CancellationTokenSource;
        private CancellationTokenSource m_LoadingCancellationTokenSource;

        public void Initialize(GameObject gameObject) {
            Assert.IsNotNull(gameObject);

            m_GameObject = gameObject;
            m_GameObject.TryGetComponent(out m_Animator);
        }

        public async UniTask Load(IAnimationScheme scheme) {
            Assert.IsNotNull(scheme);
            Assert.IsNull(m_Scheme);

            if (!m_GameObject.TryGetComponent(out m_Animator)) {
                m_Animator = m_GameObject.AddComponent<Animator>();
            }
            m_Scheme = scheme;
            if (scheme.ControllerAssetName != null) {
                m_LoadingCancellationTokenSource = new CancellationTokenSource();
                m_ControllerAssetHandle = await AssetService.Instance.Load<RuntimeAnimatorController>(AssetRequest.Instantiate(scheme.ControllerAssetName, m_LoadingCancellationTokenSource.Token));
                if (m_LoadingCancellationTokenSource != null) {
                    m_LoadingCancellationTokenSource = null;
                }
                if (m_ControllerAssetHandle != AssetHandle.None) {
                    var controller = m_ControllerAssetHandle.Content;
                    var overrideController = new AnimatorOverrideController(controller);
                    m_Animator.runtimeAnimatorController = overrideController;
                    await scheme.Load(overrideController);
                }
            }
        }

        public UniTask Play(AnimationState state) {
            if (m_Scheme.TryMoveNextExtension(state, out var result)) {
                state = result.Item1;
            }
            return DoPlay(state);
        }

        public async UniTask PlayOverride(AnimationState state, AnimationClipName clipName, string assetKey) {
            if (m_Scheme.TryMoveNextExtension(state, out var result)) {
                state = result.Item1;
                clipName = result.Item3;
            }
            await m_Scheme.Override(clipName, assetKey);
            await DoPlay(state);
        }

        private UniTask DoPlay(AnimationState state) {
            if (m_Animator != null && m_Animator.runtimeAnimatorController != null) {
                if (m_Animator.HasState(state.Layer, state.NameHash)) {
                    m_Animator.Play(state.NameHash, state.Layer);
                    return Wait(state);
                }
            }
            return UniTask.CompletedTask;
        }

        public UniTask Trigger(AnimationTrigger trigger) {
            AnimationState state = default;
            if (m_Scheme.TryMoveNextExtension(trigger, out var result)) {
                state = result.Item1;
                trigger = result.Item2;
            }
            return DoTrigger(trigger, state);
        }

        public async UniTask TriggerOverride(AnimationTrigger trigger, AnimationClipName clipName, string assetKey) {
            AnimationState state = default;
            if (m_Scheme.TryMoveNextExtension(trigger, out var result)) {
                state = result.Item1;
                trigger = result.Item2;
                clipName = result.Item3;
            }
            await m_Scheme.Override(clipName, assetKey);
            await DoTrigger(trigger, state);
        }

        private UniTask DoTrigger(AnimationTrigger trigger, AnimationState state) {
            if (m_Animator != null && m_Animator.runtimeAnimatorController != null) {
                if (m_Animator.HasState(state.Layer, state.NameHash)) {
                    m_Animator.SetTrigger(trigger.ParameterID);
                    return Wait(state);
                }
            }
            return UniTask.CompletedTask;
        }

        private async UniTask Wait(AnimationState state) {
            if (m_CancellationTokenSource != null) {
                m_CancellationTokenSource.Cancel();
            }
            var cancellationTokenSource = m_CancellationTokenSource = new CancellationTokenSource();
            await KHBD.Context.WaitUntil(() => m_Animator == null || m_Animator.GetCurrentAnimatorStateInfo(state.Layer).shortNameHash == state.NameHash || cancellationTokenSource.IsCancellationRequested);
            await KHBD.Context.WaitUntil(() => m_Animator == null || m_Animator.GetCurrentAnimatorStateInfo(state.Layer).shortNameHash != state.NameHash || cancellationTokenSource.IsCancellationRequested);
        }

        protected override void Dispose() {
            if (m_LoadingCancellationTokenSource != null) {
                m_LoadingCancellationTokenSource.Cancel();
                m_LoadingCancellationTokenSource = null;
            }

            if (m_CancellationTokenSource != null) {
                m_CancellationTokenSource.Cancel();
                m_CancellationTokenSource = null;
            }

            if (m_Scheme is IDisposable disposable) {
                disposable.Dispose();
            }
            m_Scheme = null;

            if (m_ControllerAssetHandle != AssetHandle.None) {
                m_ControllerAssetHandle.Release();
                m_ControllerAssetHandle = AssetHandle.None;
            }

            Handle = AnimationHandle.None;
            m_GameObject = null;

            m_Animator = null;
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Animation {

    public interface IAnimationInternal {

        void Initialize(GameObject gameObject);
    }
}
